﻿/**
 * @file XWidgetP2J.cpp
 * Copyright (c) Gaaagaa. All rights reserved.
 * 
 * @author  : Gaaagaa
 * @date    : 2020-12-20
 * @version : 1.0.0.0
 * @brief   : 实现 PDF转图片 面板部件。
 */

#include "XWidgetJ2P.h"
#include "ui_XWidgetJ2P.h"

#include "xtypes.h"
#include "XPDF_wrapper.h"

#include <QItemSelectionModel>
#include <QPainter>
#include <QFileDialog>
#include <QDir>
#include <QMessageBox>
#include <QProgressDialog>

#include <QDebug>

////////////////////////////////////////////////////////////////////////////////

/**********************************************************/
/**
 * @brief
 * 保持纵横比例（maintain aspect ratio）的情况下，
 * 在限定的最大尺寸值（宽高）中，计算出缩放后的最大尺寸值（宽高）。
 *
 * @note xut_mcx 和 xut_mcy 同时为 0 时，xut_rcx 和 xut_rcy 值不做修改。
 *
 * @param [in,out] xit_rcx : 入参，原尺寸宽度；回参，操作成功返回的建议宽度。
 * @param [in,out] xit_rcy : 入参，原尺寸高度；回参，操作成功返回的建议高度。
 * @param [in    ] xit_mcx : 限定的最大尺寸宽度。为 0 时，以 xut_mcy 为准。
 * @param [in    ] xit_mcy : 限定的最大尺寸高度。为 0 时，以 xut_mcx 为准。
 *
 * @return x_bool_t
 *         - 成功，返回 X_TRUE；
 *         - 失败，返回 X_FALSE。
 */
static x_bool_t mar_resize(
                x_int32_t & xit_rcx,
                x_int32_t & xit_rcy,
                x_int32_t   xit_mcx,
                x_int32_t   xit_mcy)
{
    x_float_t xft_mrx = 0.0F;
    x_float_t xft_mry = 0.0F;

    if ((xit_rcx <= 0) || (xit_rcy <= 0))
    {
        return X_FALSE;
    }

    if (0 != xit_mcx)
    {
        if (0 != xit_mcy)
        {
            xft_mrx = xit_mcx / (x_float_t)(xit_rcx);
            xft_mry = xit_mcy / (x_float_t)(xit_rcy);

            if ((xft_mrx - xft_mry) > 1e-6F)
            {
                xit_rcx = (x_int32_t)(xit_rcx * xft_mry);
                xit_rcy = xit_mcy;
            }
            else
            {
                xit_rcx = xit_mcx;
                xit_rcy = (x_int32_t)(xit_rcy * xft_mrx);
            }
        }
        else
        {
            xit_rcx = xit_mcx;
            xit_rcy = (x_int32_t)(xit_rcy * xit_mcx / (x_float_t)(xit_rcx));
        }
    }
    else
    {
        if (0 != xit_mcy)
        {
            xit_rcx = (x_int32_t)(xit_rcx * xit_mcy / (x_float_t)(xit_rcy));
            xit_rcy = xit_mcy;
        }
    }

    return X_TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// XWidgetJ2P

//====================================================================

// 
// XWidgetJ2P : public interfaces
// 

XWidgetJ2P::XWidgetJ2P(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::XWidgetJ2P)
{
    ui->setupUi(this);

    //======================================

    ui->comboBox_PageSize->addItem(tr("与图片相同"));
    ui->comboBox_PageSize->addItem(tr("固定宽高"));
    ui->comboBox_PageSize->addItem(tr("限定宽高"));
    ui->comboBox_PageSize->setCurrentIndex(0);
    on_comboBox_PageSize_currentIndexChanged(0);

    QRegExpValidator * xRegDigitValidator =
            new QRegExpValidator(QRegExp("^[1-9][0-9]+$"), this);

    ui->lineEdit_PageW->setValidator(xRegDigitValidator);
    ui->lineEdit_PageH->setValidator(xRegDigitValidator);

    //======================================

    connect(ui->treeView_ImageList->model(),
            &QAbstractItemModel::rowsInserted,
            this,
            &XWidgetJ2P::on_updateImageListCount);

    connect(ui->treeView_ImageList->model(),
            &QAbstractItemModel::rowsRemoved,
            this,
            &XWidgetJ2P::on_updateImageListCount);

    connect(ui->treeView_ImageList->selectionModel(),
            &QItemSelectionModel::currentRowChanged,
            this,
            &XWidgetJ2P::on_imageListCurrentRowChanged);

    //======================================
}

XWidgetJ2P::~XWidgetJ2P(void)
{
    delete ui;
}

//====================================================================

//
// overrides
//

void XWidgetJ2P::showEvent(QShowEvent *event)
{
    QWidget::showEvent(event);

    int cx = ui->treeView_ImageList->width() / 10;
    ui->treeView_ImageList->setColumnWidth(0, 1 * cx);
    ui->treeView_ImageList->setColumnWidth(1, 6 * cx);
    ui->treeView_ImageList->setColumnWidth(2, 2 * cx);
}

//====================================================================

// 
// XWidgetJ2P : slots
// 

void XWidgetJ2P::on_updateImageListCount(
        const QModelIndex &parent, int first, int last)
{
    int count = ui->treeView_ImageList->rowCount();
    ui->lineEdit_ImageCount->setText(QString("%1").arg(count));
}

void XWidgetJ2P::on_imageListCurrentRowChanged(
        const QModelIndex &current, const QModelIndex &previous)
{
    if (current.isValid())
    {
        QString strImage = ui->treeView_ImageList->getImagePath(current.row());
        if (ui->widget_PageImage->setCurrent(strImage))
        {
            if (ui->lineEdit_PageW->text().isEmpty())
            {
                ui->lineEdit_PageW->setText(
                            QString(tr("%1"))
                            .arg(ui->widget_PageImage->image().width()));
            }

            if (ui->lineEdit_PageH->text().isEmpty())
            {
                ui->lineEdit_PageH->setText(
                            QString(tr("%1"))
                            .arg(ui->widget_PageImage->image().height()));
            }
        }
    }
    else
    {
        ui->widget_PageImage->setCurrent(tr(""));
    }
}

void XWidgetJ2P::on_btn_IListByFolder_clicked()
{
    QString strDir = QFileDialog::getExistingDirectory(this, tr("选择图片目录"));
    if (strDir.isEmpty())
    {
        return;
    }

    ui->treeView_ImageList->importFromDir(strDir);
}

void XWidgetJ2P::on_btn_IListByFile_clicked()
{
    QStringList strFiles =
            QFileDialog::getOpenFileNames(
                this,
                tr("添加页面图片"),
                tr(""),
                tr("页面图片文件(*.jpg;*.jpeg)"));
    strFiles.sort(Qt::CaseInsensitive);
    ui->treeView_ImageList->importImages(strFiles);
}

void XWidgetJ2P::on_btn_IListRemove_clicked()
{
    QModelIndex index = ui->treeView_ImageList->currentIndex();
    if (index.isValid())
    {
        QModelIndex aIndex =  ui->treeView_ImageList->indexAbove(index);
        QModelIndex cIndex = index;

        ui->treeView_ImageList->remove(index.row());

        if (cIndex.row() < ui->treeView_ImageList->rowCount())
        {
            aIndex = cIndex;
        }

        ui->treeView_ImageList->setCurrentIndex(aIndex);
    }
}

void XWidgetJ2P::on_btn_IListCleanup_clicked()
{
    ui->treeView_ImageList->cleanup();
    ui->widget_PageImage->setCurrent(tr(""));
}

void XWidgetJ2P::on_comboBox_PageSize_currentIndexChanged(int index)
{
    QString strText = ui->comboBox_PageSize->currentText();
    if (tr("与图片相同") == strText)
    {
        ui->label_PageW->hide();
        ui->lineEdit_PageW->hide();
        ui->label_PageH->hide();
        ui->lineEdit_PageH->hide();
    }
    else
    {
        ui->label_PageW->show();
        ui->lineEdit_PageW->show();
        ui->label_PageH->show();
        ui->lineEdit_PageH->show();
    }
}

void XWidgetJ2P::on_btn_PDFSave_clicked()
{
    //======================================
    // 读取图片列表的各个图片信息

    XImageInfoList xInfoList;
    if (ui->treeView_ImageList->queryImageList(xInfoList) <= 0)
    {
        QMessageBox::information(
                    this,
                    tr("提示"),
                    tr("未能从图片列表中取到图片文件，请先 添加（或 导入）图片！"));
        ui->btn_IListByFile->setFocus();
        return;
    }

    //======================================
    // 页面设置参数

    x_int32_t xit_psize_type = ui->comboBox_PageSize->currentIndex();
    x_int32_t xit_psize_cx   = ui->lineEdit_PageW->text().toInt();
    x_int32_t xit_psize_cy   = ui->lineEdit_PageH->text().toInt();
    if ((xit_psize_type < 0) || (xit_psize_type > 2))
    {
        QMessageBox::information(
                    this,
                    tr("提示"),
                    tr("页面的宽高设置模式，尚未支持！"));
        ui->comboBox_PageSize->setFocus();
        return;
    }

    //======================================
    // 确定文件保存路径

    QString strFile =
            QFileDialog::getSaveFileName(
                this,
                tr("保存到 PDF 文件"),
                tr(""),
                tr("PDF文件(*.pdf)"));
    if (strFile.isEmpty())
    {
        return;
    }

    //======================================
    // 创建 PDF 文件的写操作对象

    XPDF_writer_t xpdf_writer;
    x_errno_t xerr_no = xpdf_writer.create(X_NULL);
    if (XERR_FAILED(xerr_no))
    {
        QMessageBox::critical(
                    this,
                    tr("系统错误"),
                    QString(tr("创建 PDF 生成器对象时 失败，错误码：%1"))
                        .arg(XPDF_writer_t::xerrno_name(xerr_no)));
        return;
    }

    //======================================

    // 用于收集 图片放置到 PDF 页面时 产生的错误信息
    std::vector< QString > xVecError;

    // 操作的进度对话框
    QProgressDialog progress(
                QString(tr("正在将所有图片合成到 PDF 文件......\n %1 ")).arg(strFile),
                tr("取消"),
                0,
                static_cast< int >(xInfoList.size()),
                this);
    progress.setWindowModality(Qt::WindowModal);

    // 开始遍历各个图片信息，依次插入到 PDF 文件中
    x_int32_t xit_page_no = 0;
    for (size_t iter = 0; iter < xInfoList.size(); ++iter)
    {
        progress.setValue(static_cast< int >(iter));

        if (progress.wasCanceled())
            break;

        //======================================
        // 构建 PDF 页面所需的工作参数

        XImageInfo & xInfo = xInfoList.at(iter);

        // 图片路径
        std::string xstr_path = xInfo.first.toLocal8Bit().data();

        // 页面宽高
        x_int32_t xit_cx = 0;
        x_int32_t xit_cy = 0;
        switch (xit_psize_type)
        {
        case 0 :
            {
                xit_cx = xInfo.second.width();
                xit_cy = xInfo.second.height();
            }
            break;

        case 1 :
            {
                xit_cx = (xit_psize_cx > 0) ? xit_psize_cx : xInfo.second.width();
                xit_cy = (xit_psize_cy > 0) ? xit_psize_cy : xInfo.second.height();
            }
            break;

        case 2 :
            {
                xit_cx = xInfo.second.width();
                xit_cy = xInfo.second.height();

                mar_resize(xit_cx, xit_cy, xit_psize_cx, xit_psize_cy);
            }
            break;
        }

        //======================================
        // 构建 PDF 页面

        xerr_no = xpdf_writer.insert_page_by_jpegfile(
                    xit_page_no, xstr_path.c_str(), xit_cx, xit_cy);
        if (XERR_FAILED(xerr_no))
        {
            xVecError.push_back(
                QString(tr("文件：%1 ，宽高：%2 x %3 ，错误码：%4"))
                        .arg(xInfo.first)
                        .arg(xInfo.second.width())
                        .arg(xInfo.second.height())
                        .arg(XPDF_writer_t::xerrno_name(xerr_no)));
            continue;
        }

        xit_page_no += 1;

        //======================================
    }

    progress.setValue(static_cast< int >(xInfoList.size()));

    // 保存 PDF 文件到指定路径中
    if (xit_page_no > 0)
    {
        std::string xstr_path = strFile.toLocal8Bit().data();
        xerr_no = xpdf_writer.save_to_file(xstr_path.c_str());
        if (XERR_FAILED(xerr_no))
        {
            QMessageBox::critical(
                        this,
                        tr("错误"),
                        QString(tr("保存PDF文件至 %1 时失败，错误码：%2"))
                            .arg(strFile)
                            .arg(XPDF_writer_t::xerrno_name(xerr_no)));
            return;
        }
    }

    //======================================
    // 报告过程中产生的错误信息

    if (!xVecError.empty())
    {
        x_int32_t xit_count = 0;

        QString strError;
        for (std::vector< QString >::const_iterator
             citer = xVecError.begin();
             citer != xVecError.end();
             ++citer)
        {
            // 若产生的错误信息超过 10 条，则终止信息后续的信息
            // 以保存 告警消息窗口 不至于过大
            if (++xit_count >= 10)
            {
                strError += tr("......");
                break;
            }

            strError += *citer + tr("\n");
        }

        QMessageBox::warning(
                    this,
                    tr("警告"),
                    QString(tr("合成至 PDF 文件：%1 时，\n"
                               "有 [ %2 ] 张图片产生错误，未能存入PDF中，"
                               "如下所示：\n%3"))
                        .arg(strFile)
                        .arg(xVecError.size())
                        .arg(strError));
    }

    //======================================
}
